﻿//-----------------------------------------------------------------------
// <copyright file="RuleSet.cs" company="tuliprules.org">
//     Copyright (c) 2010 tuplelister, tuliprules.org.
// </copyright>
// <summary>This is the RuleSet class.</summary>
//-----------------------------------------------------------------------

namespace Tulip.Rules
{
    using System;
    using System.Collections.ObjectModel;
    using System.Globalization;
    using System.Reflection;
    using System.Xml;
    using System.Xml.Schema;
    using System.Xml.Serialization;

    /// <summary>
    /// A rule set containing arbitrary content managed by a parser.
    /// </summary>
    /// <remarks>
    /// <para>The content is always represented as a string, which may be encoded as
    /// required.  Presentation is parsed into a document model by an instance of a parser.
    /// The parser can also manage a collection of rule objects to represent individual
    /// rules.</para>
    /// <para>This class can be used as abase class for specialised implementations of rule sets.</para>
    /// </remarks>
    [Serializable]
    [XmlSchemaProvider("GetRuleSetSchema")]
    [XmlRoot("RuleSet", Namespace = "http://tuliprules/rulesets/2010/12", IsNullable = true)]
    public class RuleSet : IRuleObject, IXmlSerializable
    {
        /// <summary>
        /// Initializes a new instance of the RuleSet class.
        /// </summary>
        public RuleSet()
        {
            this.RuleSetInfo = new RuleSetInfo();
        }

        /// <summary>
        /// Initializes a new instance of the RuleSet class.
        /// </summary>
        /// <param name="name">
        /// Name of the rule set.
        /// </param>
        public RuleSet(string name)
        {
            this.RuleSetInfo = new RuleSetInfo(name);
        }

        /// <summary>
        /// Initializes a new instance of the RuleSet class.
        /// </summary>
        /// <param name="name">
        /// Name of the rule set.
        /// </param>
        /// <param name="majorVersion">
        /// The major version component of the version number.
        /// </param>
        /// <param name="minorVersion">
        /// The minor version component of the version number.
        /// </param>
        /// <param name="build">
        /// The build component of the version number.
        /// </param>
        /// <param name="revision">
        /// The revision component of the version number.
        /// </param>
        public RuleSet(string name, int majorVersion, int minorVersion, int build, int revision)
        {
            this.RuleSetInfo = new RuleSetInfo(name, majorVersion, minorVersion, build, revision);
        }

        /// <summary>
        /// Initializes a new instance of the RuleSet class.
        /// </summary>
        /// <param name="name">
        /// Name of the rule set.
        /// </param>
        /// <param name="version">
        /// The version specifer for the rule set.
        /// </param>
        public RuleSet(string name, Version version)
        {
            this.RuleSetInfo = new RuleSetInfo(name, version);
        }

        /// <summary>
        /// Gets a URI that can be used as a type specifer for the rule set.
        /// </summary>
        /// <value>A URI that can be used as a type specifer for the rule set.</value>
        public Uri Type
        {
            get
            {
                return this.Parser == null ? null : this.Parser.RuleSetType;
            }
        }

        /// <summary>
        /// Gets or sets the name of the rule set.
        /// </summary>
        /// <value>
        /// The name of the rule set.
        /// </value>
        public virtual string Name
        {
            get
            {
                return this.RuleSetInfo.Name;
            }

            set
            {
                this.RuleSetInfo.Name = value;
            }
        }

        /// <summary>
        /// Gets or sets the major version component of the version number.
        /// </summary>
        /// <value>
        /// The major version component of the version number.
        /// </value>
        public virtual int MajorVersion
        {
            get
            {
                return this.RuleSetInfo.MajorVersion;
            }

            set
            {
                this.RuleSetInfo.MajorVersion = value;
            }
        }

        /// <summary>
        /// Gets or sets the minor version component of the version number.
        /// </summary>
        /// <value>
        /// The minor version component of the version number.
        /// </value>
        public virtual int MinorVersion
        {
            get
            {
                return this.RuleSetInfo.MinorVersion;
            }

            set
            {
                this.RuleSetInfo.MinorVersion = value;
            }
        }

        /// <summary>
        /// Gets or sets the build component of the version number.
        /// </summary>
        /// <value>
        /// The build component of the version number.
        /// </value>
        public virtual int Build
        {
            get
            {
                return this.RuleSetInfo.Build;
            }

            set
            {
                this.RuleSetInfo.Build = value;
            }
        }

        /// <summary>
        /// Gets or sets the revision component of the version number.
        /// </summary>
        /// <value>
        /// The revision component of the version number.
        /// </value>
        public virtual int Revision
        {
            get
            {
                return this.RuleSetInfo.Revision;
            }

            set
            {
                this.RuleSetInfo.Revision = value;
            }
        }

        /// <summary>
        /// Gets the version number.
        /// </summary>
        /// <value>
        /// The version number.
        /// </value>
        public virtual Version Version
        {
            get
            {
                return this.RuleSetInfo.Version;
            }
        }

        /// <summary>
        /// Gets the presentation representation of the rule set.
        /// </summary>
        /// <value>
        /// The content of the rule set.
        /// </value>
        public virtual string Presentation
        {
            get
            {
                return this.Parser == null ? string.Empty : this.Parser.Presentation;
            }
        }

        /// <summary>
        /// Gets a read-only collection of rules.
        /// </summary>
        /// <value>
        /// A read-only collection of rules.
        /// </value>
        public virtual ReadOnlyCollection<Rule> Rules
        {
            get
            {
                return this.Parser == null ? null : this.Parser.Rules;
            }
        }

        /// <summary>
        /// Gets or sets the description of this rule set as rule set information.
        /// </summary>
        /// <value>The description of this rule set as rule set information.</value>
        public RuleSetInfo RuleSetInfo { get; set; }

        /// <summary>
        /// Gets or sets a parser for the rule or rule set.
        /// </summary>
        /// <value>
        /// A parser for the rule or rule set.
        /// </value>
        public virtual IRuleSetParser Parser { get; set; }

        /// <summary>
        /// Returns the schema used to represent rule sets.
        /// </summary>
        /// <param name="xmlSchemaSet">
        /// A cache of XML Schema definition language (XSD) schemas.
        /// </param>
        /// <returns>
        /// The qualified name of the schema used to represent rule sets.
        /// </returns>
        public static XmlQualifiedName GetRuleSetSchema(XmlSchemaSet xmlSchemaSet)
        {
            if (xmlSchemaSet == null)
            {
                throw new ArgumentNullException("xmlSchemaSet", Properties.Resources.ExValueNotNull);
            }

            try
            {
                var assembly = Assembly.GetExecutingAssembly();

                using (var stream = assembly.GetManifestResourceStream(Properties.Resources.RuleSetSchema))
                {
                    if (stream == null)
                    {
                        throw new RulesException();
                    }

                    var xsdReader = new XmlTextReader(stream);
                    var xs = XmlSchema.Read(xsdReader, null);
                    xmlSchemaSet.XmlResolver = new XmlUrlResolver();
                    xmlSchemaSet.Add(xs);
                    return new XmlQualifiedName("RuleSetType", Properties.Resources.Namespace);
                }
            }
            catch (Exception ex)
            {
                throw new RulesException(
                    string.Format(
                        CultureInfo.CurrentCulture,
                        Properties.Resources.ExSchemaNotRetrieved,
                        "RuleSetType"),
                    ex);
            }
        }

        /// <summary>
        /// This method is reserved and should not be used.
        /// </summary>
        /// <returns>
        /// An XmlSchema that describes the XML representation of the object that is produced 
        /// by the WriteXml method and consumed by the ReadXml method.
        /// </returns>
        public virtual XmlSchema GetSchema()
        {
            return null;
        }

        /// <summary>
        /// Generates a RuleSet from its XML representation.
        /// </summary>
        /// <param name="reader">
        /// The XmlReader stream from which the RuleSet is deserialized.
        /// </param>
        public virtual void ReadXml(XmlReader reader)
        {
            if (reader.LocalName != "RuleSet")
            {
                reader.ReadToFollowing("RuleSet", Properties.Resources.Namespace);
            }

            if (reader.MoveToFirstAttribute())
            {
                while (true)
                {
                    switch (reader.LocalName)
                    {
                        case "name":
                            this.Name = reader.ReadContentAsString();
                            break;
                        case "majorVersion":
                            this.MajorVersion = reader.ReadContentAsInt();
                            break;
                        case "minorVersion":
                            this.MinorVersion = reader.ReadContentAsInt();
                            break;
                        case "build":
                            this.Build = reader.ReadContentAsInt();
                            break;
                        case "revision":
                            this.Revision = reader.ReadContentAsInt();
                            break;
                    }

                    if (!reader.MoveToNextAttribute())
                    {
                        break;
                    }
                }
            }

            var parserAssemblyName = string.Empty;
            var parserTypeName = string.Empty;
            var parserContent = string.Empty;

            while (reader.Read())
            {
                switch (reader.LocalName)
                {
                    case "Parser":

                        if (reader.MoveToFirstAttribute())
                        {
                            while (true)
                            {
                                switch (reader.LocalName)
                                {
                                    case "assemblyName":
                                        parserAssemblyName = reader.ReadContentAsString();
                                        break;
                                    case "typeName":
                                        parserTypeName = reader.ReadContentAsString();
                                        break;
                                }

                                if (!reader.MoveToNextAttribute())
                                {
                                    break;
                                }
                            }
                        }

                        break;
                    case "Presentation":
                        parserContent = reader.ReadElementContentAsString();
                        break;
                }
            }

            if (string.IsNullOrWhiteSpace(parserAssemblyName) && string.IsNullOrWhiteSpace(parserTypeName))
            {
                return;
            }

            var proxy = Activator.CreateInstance(parserAssemblyName, parserTypeName);
            this.Parser = (RuleSetParser)proxy.Unwrap();

            if (string.IsNullOrWhiteSpace(parserContent))
            {
                return;
            }

            this.Parser.Parse(parserContent);
        }

        /// <summary>
        /// Converts a RuleSet into its XML representation.
        /// </summary>
        /// <param name="writer">
        /// The XmlWriter stream to which the RuleSet is serialized.
        /// </param>
        public virtual void WriteXml(XmlWriter writer)
        {
            if (writer == null)
            {
                return;
            }

            //// writer.WriteStartElement("RuleSet", Properties.Resources.Namespace);
            writer.WriteAttributeString("name", this.Name);
            writer.WriteAttributeString("majorVersion", Convert.ToString(this.MajorVersion));
            writer.WriteAttributeString("minorVersion", Convert.ToString(this.MinorVersion));
            writer.WriteAttributeString("build", Convert.ToString(this.Build));
            writer.WriteAttributeString("revision", Convert.ToString(this.Revision));
            writer.WriteStartElement("Parser", Properties.Resources.Namespace);

            if (this.Parser != null)
            {
                writer.WriteAttributeString("assemblyName", this.Parser.GetType().Assembly.FullName);
                writer.WriteAttributeString("typeName", this.Parser.GetType().FullName);
            }

            writer.WriteEndElement();
            writer.WriteStartElement("Presentation", Properties.Resources.Namespace);
            writer.WriteString(this.Presentation);
            writer.WriteEndElement();
            ////writer.WriteEndElement();
        }
    }
}
